package cn.fr4nk.crawler.proxy;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import cn.fr4nk.crawler.utils.CsvUtil;

public class BaseProxyPool implements ProxyPool {

	private static volatile BaseProxyPool instance = null;

	private static Log log = LogFactory.getLog(BaseProxyPool.class);

	private static int MAX_RETRY_TIMES = 3;

	protected static boolean checkIp(String ip) {
		String ip_pattern = "^(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[1-9])\\."
				+ "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\." + "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."
				+ "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)$";
		Pattern pattern = Pattern.compile(ip_pattern);
		Matcher matcher = pattern.matcher(ip);
		return matcher.matches();
	}

	public static BaseProxyPool getInstance() {
		if (instance == null) {
			synchronized (BaseProxyPool.class) {
				if (null == instance) {
					instance = new BaseProxyPool();
				}
			}
		}
		return instance;
	}

	private volatile String csvFilePath;

	private ConcurrentMap<String, Proxy> pool;

	private BaseProxyPool() {
		pool = new ConcurrentHashMap<String, Proxy>();
	}

	@Override
	public void add(Proxy proxy) throws IllegalArgumentException {
		if (!checkProxy(proxy)) {
			throw new IllegalArgumentException("Invalid proxy: null or no ip");
		}
		String key = proxy.getIpPort();
		pool.putIfAbsent(key, proxy);
	}

	@Override
	public void add(String prefix, Proxy proxy) {
		if (StringUtils.isEmpty(prefix)) {
			add(proxy);
			return;
		}
		if (!checkProxy(proxy)) {
			throw new IllegalArgumentException("Invalid proxy: null or no ip");
		}
		String key = prefix + proxy.getIpPort();
		pool.putIfAbsent(key, proxy);
	}

	@Override
	public void addList(List<Proxy> proxy) throws IllegalArgumentException {
		for (Proxy p : proxy) {
			add(p);
		}
	}

	@Override
	public void addList(String prefix, List<Proxy> proxy) throws IllegalArgumentException {
		for (Proxy p : proxy) {
			add(prefix, p);
		}
	}

	public void asyncSaveCsv(final String fileName) {
		if (StringUtils.isEmpty(fileName)) {
			log.warn("Save Failed: Empty csv filePath");
			return;
		}
		Thread saveThread = new Thread(new Runnable() {
			@Override
			public void run() {
				saveAsCsv(fileName);
			}
		});
		saveThread.start();
	}

	public void asyncSaveCsv(final String prefix, final String fileName) {
		if (StringUtils.isEmpty(fileName)) {
			log.warn("Save Failed: Empty csv filePath");
			return;
		}
		Thread saveThread = new Thread(new Runnable() {
			@Override
			public void run() {
				saveAsCsv(prefix, fileName);
			}
		});
		saveThread.start();
	}

	protected boolean checkProxy(Proxy proxy) {

		if (null == proxy || StringUtils.isEmpty(proxy.getIp()) || !checkIp(proxy.getIp()) || proxy.getPort() < 1) {
			return false;
		}
		return true;
	}

	@Override
	public Proxy get() {
		Proxy proxy = null;
		synchronized (this) {
			String[] keys = pool.keySet().toArray(new String[pool.size()]);
			if (keys != null && keys.length > 0) {
				String key = keys[new Random().nextInt(keys.length)];
				proxy = pool.get(key);
			}
		}
		return proxy;
	}

	@Override
	public Proxy get(String prefix) {
		Proxy proxy = null;
		synchronized (this) {
			String[] keys = pool.keySet().toArray(new String[pool.size()]);

			List<String> targets = new ArrayList<String>();
			for (String key : keys) {
				if (key.startsWith(prefix)) {
					targets.add(key);
				}
			}
			Object[] targetStr =  targets.toArray();
			if (targetStr != null && targetStr.length > 0) {
				String key2 = targetStr[new Random().nextInt(targetStr.length)].toString();
				proxy = pool.get(key2);
			}
		}
		return proxy;
	}


	public String getCsvFilePath() {
		return csvFilePath;
	}

	public Proxy getFromKey(String key) {
		return pool.get(key);
	}

	public String getRandomKey() {
		String proxy = "";
		synchronized (this) {
			String[] keys = pool.keySet().toArray(new String[pool.size()]);
			if (keys != null && keys.length > 0) {
				return keys[new Random().nextInt(keys.length)];
			}
		}
		return proxy;
	}

	@Override
	public void increaseProxyFails(Proxy proxy) throws IllegalArgumentException {
		if (!checkProxy(proxy)) {
			throw new IllegalArgumentException("Invalid proxy");
		}
		String key = proxy.getIpPort();
		if (pool.containsKey(key)) {
			synchronized (this) {
				Proxy cachedProxy = pool.get(key);
				int fails = cachedProxy.getFailedTimes() + 1;
				cachedProxy.setFailedTimes(fails);
				if (fails < MAX_RETRY_TIMES) {
					pool.replace(key, cachedProxy);
				} else {
					log.debug("remove proxy: " + key);
					pool.remove(key);
				}
			}
		} else {
			throw new IllegalArgumentException("proxy not in pool");
		}
	}

	public void loadFromCsv(String fileName) {
		loadFromCsv(null, fileName);
	}

	public void loadFromCsv(String prefix, String fileName) {
		List<String[]> content = CsvUtil.read(fileName);
		if (null != content) {
			for (String[] arr : content) {
				if (null != arr && arr.length >= 2) {
					Proxy p = new Proxy(arr[0], Integer.valueOf(arr[1]));
					log.debug("load proxy -> " + p.getIpPort());
					try {
						add(prefix, p);
					} catch (IllegalArgumentException e) {
						log.warn(e.getMessage());
					}
				}
			}
		}
	}

	@Override
	public void remove(Proxy proxy) throws IllegalArgumentException {

		if (!checkProxy(proxy)) {
			throw new IllegalArgumentException("Invalid proxy");
		}
		synchronized (this) {
			pool.remove(proxy.getIpPort());
		}
	}

	public void remove(String key) throws IllegalArgumentException {
		synchronized (this) {
			pool.remove(key);
		}
	}

	@Override
	public void remove(String ip, int port) throws IllegalArgumentException {
		Proxy p = new Proxy(ip, port);
		if (!checkProxy(p)) {
			throw new IllegalArgumentException("Invalid proxy ip or port");
		}
		String key = p.getIpPort();
		synchronized (this) {
			pool.remove(key);
		}
	}

	public void remove(String prefix, Proxy proxy) throws IllegalArgumentException {
		if (StringUtils.isEmpty(prefix)) {
			remove(proxy);
			return;
		}
		if (!checkProxy(proxy)) {
			throw new IllegalArgumentException("Invalid proxy");
		}
		synchronized (this) {
			pool.remove(prefix + proxy.getIpPort());
		}
	}

	/**
	 * 保存为csv格式
	 * 
	 * @param fileName
	 */
	public void saveAsCsv(String fileName) {
		List<String[]> content = new ArrayList<String[]>();
		for (String key : pool.keySet()) {
			Proxy p = pool.get(key);
			String[] one_p = new String[2];
			one_p[0] = p.getIp();
			one_p[1] = "" + p.getPort();
			content.add(one_p);
		}
		CsvUtil.write(fileName, content);
	}

	public void saveAsCsv(String prefix, String fileName) {
		List<String[]> content = new ArrayList<String[]>();
		for (String key : pool.keySet()) {
			if (key.startsWith(prefix)) {
				Proxy p = pool.get(key);
				String[] one_p = new String[2];
				one_p[0] = p.getIp();
				one_p[1] = "" + p.getPort();
				content.add(one_p);
			}
		}
		CsvUtil.write(fileName, content);
	}

	public void setCsvFilePath(String csvFilePath) {
		this.csvFilePath = csvFilePath;
	}

	/**
	 * 更新proxy的状态
	 */
	@Override
	public void update(Proxy proxy) throws IllegalArgumentException {
		if (!checkProxy(proxy)) {
			throw new IllegalArgumentException("Invalid proxy");
		}
		pool.replace(proxy.getIpPort(), proxy);
	}

}
